home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / phpMyAdmin / libraries / sqlparser.lib.php < prev    next >
PHP Script  |  2005-03-11  |  89KB  |  2,248 lines

  1. <?php
  2. /* $Id: sqlparser.lib.php,v 2.28 2005/03/12 13:56:24 lem9 Exp $ */
  3. // vim: expandtab sw=4 ts=4 sts=4:
  4.  
  5. /** SQL Parser Functions for phpMyAdmin
  6.  *
  7.  * Copyright 2002 Robin Johnson <robbat2@users.sourceforge.net>
  8.  * http://www.orbis-terrarum.net/?l=people.robbat2
  9.  *
  10.  * These functions define an SQL parser system, capable of understanding and
  11.  * extracting data from a MySQL type SQL query.
  12.  *
  13.  * The basic procedure for using the new SQL parser:
  14.  * On any page that needs to extract data from a query or to pretty-print a
  15.  * query, you need code like this up at the top:
  16.  *
  17.  * ($sql contains the query)
  18.  * $parsed_sql = PMA_SQP_parse($sql);
  19.  *
  20.  * If you want to extract data from it then, you just need to run
  21.  * $sql_info = PMA_SQP_analyze($parsed_sql);
  22.  *
  23.  * lem9: See comments in PMA_SQP_analyze for the returned info
  24.  *       from the analyzer.
  25.  *
  26.  * If you want a pretty-printed version of the query, do:
  27.  * $string = PMA_SQP_formatHtml($parsed_sql);
  28.  * (note that that you need to have syntax.css.php included somehow in your
  29.  * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
  30.  * href="syntax.css.php" />' at the moment.)
  31.  */
  32.  
  33.  
  34. /**
  35.  * Minimum inclusion? (i.e. for the stylesheet builder)
  36.  */
  37.  
  38. if (!isset($is_minimum_common)) {
  39.     $is_minimum_common = FALSE;
  40. }
  41.  
  42. if ($is_minimum_common == FALSE) {
  43.     /**
  44.      * Include the string library as we use it heavily
  45.      */
  46.     require_once('./libraries/string.lib.php');
  47.  
  48.     /**
  49.      * Include data for the SQL Parser
  50.      */
  51.     require_once('./libraries/sqlparser.data.php');
  52.     require_once('./libraries/mysql_charsets.lib.php');
  53.     if (!isset($mysql_charsets)) {
  54.         $mysql_charsets = array();
  55.         $mysql_charsets_count = 0;
  56.         $mysql_collations_flat = array();
  57.         $mysql_collations_count = 0;
  58.     }
  59.  
  60.     if (!defined('DEBUG_TIMING')) {
  61.         function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
  62.         {
  63.             $arr[] = array('type' => $type, 'data' => $data);
  64.             $arrsize++;
  65.         } // end of the "PMA_SQP_arrayAdd()" function
  66.     } else {
  67.         function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
  68.         {
  69.             global $timer;
  70.  
  71.             $t     = $timer;
  72.             $arr[] = array('type' => $type, 'data' => $data , 'time' => $t);
  73.             $timer = microtime();
  74.             $arrsize++;
  75.         } // end of the "PMA_SQP_arrayAdd()" function
  76.     } // end if... else...
  77.  
  78.  
  79.     /**
  80.      * Reset the error variable for the SQL parser
  81.      *
  82.      * @access public
  83.      */
  84.     // Added, Robbat2 - 13 Janurary 2003, 2:59PM
  85.     function PMA_SQP_resetError() {
  86.         global $SQP_errorString;
  87.         $SQP_errorString = '';
  88.         unset($SQP_errorString);
  89.     }
  90.  
  91.     /**
  92.      * Get the contents of the error variable for the SQL parser
  93.      *
  94.      * @return string Error string from SQL parser
  95.      *
  96.      * @access public
  97.      */
  98.     // Added, Robbat2 - 13 Janurary 2003, 2:59PM
  99.     function PMA_SQP_getErrorString() {
  100.         global $SQP_errorString;
  101.         return isset($SQP_errorString) ? $SQP_errorString : '';
  102.     }
  103.  
  104.     /**
  105.      * Check if the SQL parser hit an error
  106.      *
  107.      * @return boolean error state
  108.      *
  109.      * @access public
  110.      */
  111.     // Added, Robbat2 - 13 Janurary 2003, 2:59PM
  112.     function PMA_SQP_isError() {
  113.         global $SQP_errorString;
  114.         return isset($SQP_errorString) && !empty($SQP_errorString);
  115.     }
  116.  
  117.     /**
  118.      * Set an error message for the system
  119.      *
  120.      * @param  string  The error message
  121.      * @param  string  The failing SQL query
  122.      *
  123.      * @access private
  124.      * @scope SQL Parser internal
  125.      */
  126.     // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
  127.     function PMA_SQP_throwError($message, $sql)
  128.     {
  129.  
  130.         global $SQP_errorString;
  131.         $SQP_errorString = '<p>'.$GLOBALS['strSQLParserUserError'] . '</p>' . "\n"
  132.             . '<pre>' . "\n"
  133.             . 'ERROR: ' . $message . "\n"
  134.             . 'SQL: ' . htmlspecialchars($sql) .  "\n"
  135.             . '</pre>' . "\n";
  136.  
  137.     } // end of the "PMA_SQP_throwError()" function
  138.  
  139.  
  140.     /**
  141.      * Do display the bug report
  142.      *
  143.      * @param  string  The error message
  144.      * @param  string  The failing SQL query
  145.      *
  146.      * @access public
  147.      */
  148.     function PMA_SQP_bug($message, $sql)
  149.     {
  150.         global $SQP_errorString;
  151.         $debugstr = 'ERROR: ' . $message . "\n";
  152.         $debugstr .= 'CVS: $Id: sqlparser.lib.php,v 2.28 2005/03/12 13:56:24 lem9 Exp $' . "\n";
  153.         $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
  154.         $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ' . PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
  155.         $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
  156.         $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
  157.         $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
  158.         $debugstr .= 'SQL: ' . htmlspecialchars($sql);
  159.  
  160.         $encodedstr     = $debugstr;
  161.         if (@function_exists('gzcompress')) {
  162.             $encodedstr = gzcompress($debugstr, 9);
  163.         }
  164.         $encodedstr     = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
  165.  
  166.         $SQP_errorString .= $GLOBALS['strSQLParserBugMessage'] . '<br />' . "\n"
  167.              . '----' . $GLOBALS['strBeginCut'] . '----' . '<br />' . "\n"
  168.              . $encodedstr . "\n"
  169.              . '----' . $GLOBALS['strEndCut'] . '----' . '<br />' . "\n";
  170.  
  171.         $SQP_errorString .= '----' . $GLOBALS['strBeginRaw'] . '----<br />' . "\n"
  172.              . '<pre>' . "\n"
  173.              . $debugstr
  174.              . '</pre>' . "\n"
  175.              . '----' . $GLOBALS['strEndRaw'] . '----<br />' . "\n";
  176.  
  177.     } // end of the "PMA_SQP_bug()" function
  178.  
  179.  
  180.     /**
  181.      * Parses the SQL queries
  182.      *
  183.      * @param  string   The SQL query list
  184.      *
  185.      * @return mixed    Most of times, nothing...
  186.      *
  187.      * @global array    The current PMA configuration
  188.      * @global array    MySQL column attributes
  189.      * @global array    MySQL reserved words
  190.      * @global array    MySQL column types
  191.      * @global array    MySQL function names
  192.      * @global integer  MySQL column attributes count
  193.      * @global integer  MySQL reserved words count
  194.      * @global integer  MySQL column types count
  195.      * @global integer  MySQL function names count
  196.      * @global array    List of available character sets
  197.      * @global array    List of available collations
  198.      * @global integer  Character sets count
  199.      * @global integer  Collations count
  200.      *
  201.      * @access public
  202.      */
  203.     function PMA_SQP_parse($sql)
  204.     {
  205.         global $cfg;
  206.         global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
  207.                $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
  208.         global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
  209.  
  210.         // rabus: Convert all line feeds to Unix style
  211.         $sql = str_replace("\r\n", "\n", $sql);
  212.         $sql = str_replace("\r", "\n", $sql);
  213.  
  214.         $len = PMA_strlen($sql);
  215.         if ($len == 0) {
  216.             return array();
  217.         }
  218.  
  219.         $sql_array               = array();
  220.         $sql_array['raw']        = $sql;
  221.         $count1                  = 0;
  222.         $count2                  = 0;
  223.         $punct_queryend          = ';';
  224.         $punct_qualifier         = '.';
  225.         $punct_listsep           = ',';
  226.         $punct_level_plus        = '(';
  227.         $punct_level_minus       = ')';
  228.         $digit_floatdecimal      = '.';
  229.         $digit_hexset            = 'x';
  230.         $bracket_list            = '()[]{}';
  231.         $allpunct_list           =  '-,;:!?/.^~\*&%+<=>|';
  232.         $allpunct_list_pair      = array (
  233.             0 => '!=',
  234.             1 => '&&',
  235.             2 => ':=',
  236.             3 => '<<',
  237.             4 => '<=',
  238.             5 => '<=>',
  239.             6 => '<>',
  240.             7 => '>=',
  241.             8 => '>>',
  242.             9 => '||'
  243.         );
  244.         $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
  245.         $quote_list              = '\'"`';
  246.         $arraysize               = 0;
  247.  
  248.         while ($count2 < $len) {
  249.             $c      = PMA_substr($sql, $count2, 1);
  250.             $count1 = $count2;
  251.  
  252.             if (($c == "\n")) {
  253.                 $count2++;
  254.                 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
  255.                 continue;
  256.             }
  257.  
  258.             // Checks for white space
  259.             if (PMA_STR_isSpace($c)) {
  260.                 $count2++;
  261.                 continue;
  262.             }
  263.  
  264.             // Checks for comment lines.
  265.             // MySQL style #
  266.             // C style /* */
  267.             // ANSI style --
  268.             if (($c == '#')
  269.                 || (($count2 + 1 < $len) && ($c == '/') && (PMA_substr($sql, $count2 + 1, 1) == '*'))
  270.                 || (($count2 + 2 == $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-'))
  271.                 || (($count2 + 2 < $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
  272.                 $count2++;
  273.                 $pos  = 0;
  274.                 $type = 'bad';
  275.                 switch ($c) {
  276.                     case '#':
  277.                         $type = 'mysql';
  278.                     case '-':
  279.                         $type = 'ansi';
  280.                         $pos  = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
  281.                         break;
  282.                     case '/':
  283.                         $type = 'c';
  284.                         $pos  = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
  285.                         $pos  += 2;
  286.                         break;
  287.                     default:
  288.                         break;
  289.                 } // end switch
  290.                 $count2 = ($pos < $count2) ? $len : $pos;
  291.                 $str    = PMA_substr($sql, $count1, $count2 - $count1);
  292.                 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
  293.                 continue;
  294.             } // end if
  295.  
  296.             // Checks for something inside quotation marks
  297.             if (PMA_STR_strInStr($c, $quote_list)) {
  298.                 $startquotepos   = $count2;
  299.                 $quotetype       = $c;
  300.                 $count2++;
  301.                 $escaped         = FALSE;
  302.                 $escaped_escaped = FALSE;
  303.                 $pos             = $count2;
  304.                 $oldpos          = 0;
  305.                 do {
  306.                     $oldpos = $pos;
  307.                     $pos    = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
  308.                     // ($pos === FALSE)
  309.                     if ($pos < 0) {
  310.                         $debugstr = $GLOBALS['strSQPBugUnclosedQuote'] . ' @ ' . $startquotepos. "\n"
  311.                                   . 'STR: ' . htmlspecialchars($quotetype);
  312.                         PMA_SQP_throwError($debugstr, $sql);
  313.                         return $sql;
  314.                     }
  315.  
  316.                     // If the quote is the first character, it can't be
  317.                     // escaped, so don't do the rest of the code
  318.                     if ($pos == 0) {
  319.                         break;
  320.                     }
  321.  
  322.                     // Checks for MySQL escaping using a \
  323.                     // And checks for ANSI escaping using the $quotetype character
  324.                     if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
  325.                         $pos ++;
  326.                         continue;
  327.                     } else if (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
  328.                         $pos = $pos + 2;
  329.                         continue;
  330.                     } else {
  331.                         break;
  332.                     }
  333.                 } while ($len > $pos); // end do
  334.  
  335.                 $count2       = $pos;
  336.                 $count2++;
  337.                 $type         = 'quote_';
  338.                 switch ($quotetype) {
  339.                     case '\'':
  340.                         $type .= 'single';
  341.                         break;
  342.                     case '"':
  343.                         $type .= 'double';
  344.                         break;
  345.                     case '`':
  346.                         $type .= 'backtick';
  347.                         break;
  348.                     default:
  349.                         break;
  350.                 } // end switch
  351.                 $data = PMA_substr($sql, $count1, $count2 - $count1);
  352.                 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
  353.                 continue;
  354.             }
  355.  
  356.             // Checks for brackets
  357.             if (PMA_STR_strInStr($c, $bracket_list)) {
  358.                 // All bracket tokens are only one item long
  359.                 $count2++;
  360.                 $type_type     = '';
  361.                 if (PMA_STR_strInStr($c, '([{')) {
  362.                     $type_type = 'open';
  363.                 } else {
  364.                     $type_type = 'close';
  365.                 }
  366.  
  367.                 $type_style     = '';
  368.                 if (PMA_STR_strInStr($c, '()')) {
  369.                     $type_style = 'round';
  370.                 } elseif (PMA_STR_strInStr($c, '[]')) {
  371.                     $type_style = 'square';
  372.                 } else {
  373.                     $type_style = 'curly';
  374.                 }
  375.  
  376.                 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
  377.                 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
  378.                 continue;
  379.             }
  380.  
  381.             // Checks for punct
  382.             if (PMA_STR_strInStr($c, $allpunct_list)) {
  383.                 while (($count2 < $len) && PMA_STR_strInStr(PMA_substr($sql, $count2, 1), $allpunct_list)) {
  384.                     $count2++;
  385.                 }
  386.                 $l = $count2 - $count1;
  387.                 if ($l == 1) {
  388.                     $punct_data = $c;
  389.                 } else {
  390.                     $punct_data = PMA_substr($sql, $count1, $l);
  391.                 }
  392.  
  393.                 // Special case, sometimes, althought two characters are
  394.                 // adjectent directly, they ACTUALLY need to be seperate
  395.                 if ($l == 1) {
  396.                     $t_suffix         = '';
  397.                     switch ($punct_data) {
  398.                         case $punct_queryend:
  399.                             $t_suffix = '_queryend';
  400.                             break;
  401.                         case $punct_qualifier:
  402.                             $t_suffix = '_qualifier';
  403.                             break;
  404.                         case $punct_listsep:
  405.                             $t_suffix = '_listsep';
  406.                             break;
  407.                         default:
  408.                             break;
  409.                     }
  410.                     PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
  411.                 }
  412.                 else if (PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
  413.                     // Ok, we have one of the valid combined punct expressions
  414.                     PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
  415.                 }
  416.                 else {
  417.                     // Bad luck, lets split it up more
  418.                     $first  = $punct_data[0];
  419.                     $first2 = $punct_data[0] . $punct_data[1];
  420.                     $last2  = $punct_data[$l - 2] . $punct_data[$l - 1];
  421.                     $last   = $punct_data[$l - 1];
  422.                     if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
  423.                         $count2     = $count1 + 1;
  424.                         $punct_data = $first;
  425.                     } else if (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' ') )) {
  426.                         $count2     -= 2;
  427.                         $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
  428.                     } else if (($last == '-') || ($last == '+') || ($last == '!')) {
  429.                         $count2--;
  430.                         $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
  431.                     // TODO: for negation operator, split in 2 tokens ?
  432.                     // "select x&~1 from t"
  433.                     // becomes "select x & ~ 1 from t" ?
  434.  
  435.                     } else if ($last != '~') {
  436.                         $debugstr =  $GLOBALS['strSQPBugUnknownPunctuation'] . ' @ ' . ($count1+1) . "\n"
  437.                                   . 'STR: ' . htmlspecialchars($punct_data);
  438.                         PMA_SQP_throwError($debugstr, $sql);
  439.                         return $sql;
  440.                     }
  441.                     PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
  442.                     continue;
  443.                 } // end if... else if... else
  444.                 continue;
  445.             }
  446.  
  447.             // Checks for alpha
  448.             if (PMA_STR_isSqlIdentifier($c, FALSE) || ($c == '@')) {
  449.                 $count2 ++;
  450.  
  451.                 //TODO: a @ can also be present in expressions like
  452.                 // FROM 'user'@'%'
  453.                 // or  TO 'user'@'%'
  454.                 // in this case, the @ is wrongly marked as alpha_variable
  455.  
  456.                 $is_sql_variable         = ($c == '@');
  457.                 $is_digit                = (!$is_sql_variable) && PMA_STR_isDigit($c);
  458.                 $is_hex_digit            = ($is_digit) && ($c == '0') && ($count2 < $len) && (PMA_substr($sql, $count2, 1) == 'x');
  459.                 $is_float_digit          = FALSE;
  460.                 $is_float_digit_exponent = FALSE;
  461.  
  462.                 if ($is_hex_digit) {
  463.                     $count2++;
  464.                 }
  465.  
  466.                 while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
  467.                     $c2 = PMA_substr($sql, $count2, 1);
  468.                     if ($is_sql_variable && ($c2 == '.')) {
  469.                         $count2++;
  470.                         continue;
  471.                     }
  472.                     if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
  473.                         $count2++;
  474.                         if (!$is_float_digit) {
  475.                             $is_float_digit = TRUE;
  476.                             continue;
  477.                         } else {
  478.                             $debugstr = $GLOBALS['strSQPBugInvalidIdentifer'] . ' @ ' . ($count1+1) . "\n"
  479.                                       . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
  480.                             PMA_SQP_throwError($debugstr, $sql);
  481.                             return $sql;
  482.                         }
  483.                     }
  484.                     if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
  485.                         if (!$is_float_digit_exponent) {
  486.                             $is_float_digit_exponent = TRUE;
  487.                             $is_float_digit          = TRUE;
  488.                             $count2++;
  489.                             continue;
  490.                         } else {
  491.                             $is_digit                = FALSE;
  492.                             $is_float_digit          = FALSE;
  493.                         }
  494.                     }
  495.                     if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
  496.                         $count2++;
  497.                         continue;
  498.                     } else {
  499.                         $is_digit     = FALSE;
  500.                         $is_hex_digit = FALSE;
  501.                     }
  502.  
  503.                     $count2++;
  504.                 } // end while
  505.  
  506.                 $l    = $count2 - $count1;
  507.                 $str  = PMA_substr($sql, $count1, $l);
  508.  
  509.                 $type = '';
  510.                 if ($is_digit) {
  511.                     $type     = 'digit';
  512.                     if ($is_float_digit) {
  513.                         $type .= '_float';
  514.                     } else if ($is_hex_digit) {
  515.                         $type .= '_hex';
  516.                     } else {
  517.                         $type .= '_integer';
  518.                     }
  519.                 }
  520.                 else {
  521.                     if ($is_sql_variable != FALSE) {
  522.                         $type = 'alpha_variable';
  523.                     } else {
  524.                         $type = 'alpha';
  525.                     }
  526.                 } // end if... else....
  527.                 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize);
  528.  
  529.                 continue;
  530.             }
  531.  
  532.             // DEBUG
  533.             $count2++;
  534.  
  535.             $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len .  "\n"
  536.                       . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
  537.             PMA_SQP_bug($debugstr, $sql);
  538.             return $sql;
  539.  
  540.         } // end while ($count2 < $len)
  541.  
  542.  
  543.         if ($arraysize > 0) {
  544.           $t_next           = $sql_array[0]['type'];
  545.           $t_prev           = '';
  546.           $t_bef_prev       = '';
  547.           $t_cur            = '';
  548.           $d_next           = $sql_array[0]['data'];
  549.           $d_prev           = '';
  550.           $d_bef_prev       = '';
  551.           $d_cur            = '';
  552.           $d_next_upper     = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
  553.           $d_prev_upper     = '';
  554.           $d_bef_prev_upper = '';
  555.           $d_cur_upper      = '';
  556.         }
  557.  
  558.         for ($i = 0; $i < $arraysize; $i++) {
  559.           $t_bef_prev       = $t_prev;
  560.           $t_prev           = $t_cur;
  561.           $t_cur            = $t_next;
  562.           $d_bef_prev       = $d_prev;
  563.           $d_prev           = $d_cur;
  564.           $d_cur            = $d_next;
  565.           $d_bef_prev_upper = $d_prev_upper;
  566.           $d_prev_upper     = $d_cur_upper;
  567.           $d_cur_upper      = $d_next_upper;
  568.           if (($i + 1) < $arraysize) {
  569.             $t_next = $sql_array[$i + 1]['type'];
  570.             $d_next = $sql_array[$i + 1]['data'];
  571.             $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
  572.           } else {
  573.             $t_next       = '';
  574.             $d_next       = '';
  575.             $d_next_upper = '';
  576.           }
  577.  
  578.           //DEBUG echo "[prev: <b>".$d_prev."</b> ".$t_prev."][cur: <b>".$d_cur."</b> ".$t_cur."][next: <b>".$d_next."</b> ".$t_next."]<br />";
  579.  
  580.           if ($t_cur == 'alpha') {
  581.             $t_suffix     = '_identifier';
  582.             if (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
  583.               $t_suffix = '_identifier';
  584.             } else if (($t_next == 'punct_bracket_open_round')
  585.             && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
  586.               $t_suffix = '_functionName';
  587.             } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt))  {
  588.               $t_suffix = '_columnType';
  589.  
  590.               // Temporary fix for BUG #621357
  591.               //TODO FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
  592.               if ($d_cur_upper == 'SET' && $t_next != 'punct_bracket_open_round') {
  593.                 $t_suffix = '_reservedWord';
  594.               }
  595.               //END OF TEMPORARY FIX
  596.  
  597.               // CHARACTER is a synonym for CHAR, but can also be meant as
  598.               // CHARACTER SET. In this case, we have a reserved word.
  599.               if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
  600.                 $t_suffix = '_reservedWord';
  601.               }
  602.  
  603.               // experimental
  604.               // current is a column type, so previous must not be
  605.               // a reserved word but an identifier
  606.               // CREATE TABLE SG_Persons (first varchar(64))
  607.  
  608.               //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
  609.               //    $sql_array[$i-1]['type'] = 'alpha_identifier';
  610.               //}
  611.  
  612.             } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
  613.               $t_suffix = '_reservedWord';
  614.             } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
  615.               $t_suffix = '_columnAttrib';
  616.               // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
  617.               // it should be regarded as a reserved word.
  618.               if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
  619.                 $t_suffix = '_reservedWord';
  620.               }
  621.  
  622.               if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
  623.                 $t_suffix = '_reservedWord';
  624.               }
  625.               // Binary as character set
  626.               if ($d_cur_upper == 'BINARY' && (
  627.                   ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
  628.                   || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
  629.                   || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
  630.                   || $d_prev_upper == 'CHARSET'
  631.                   ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
  632.                   $t_suffix = '_charset';
  633.               }
  634.             } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
  635.               || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
  636.               || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
  637.               $t_suffix = '_charset';
  638.             } else {
  639.               // Do nothing
  640.             }
  641.             $sql_array[$i]['type'] .= $t_suffix;
  642.           }
  643.         } // end for
  644.  
  645.         // Stores the size of the array inside the array, as count() is a slow
  646.         // operation.
  647.         $sql_array['len'] = $arraysize;
  648.  
  649.         // Sends the data back
  650.         return $sql_array;
  651.     } // end of the "PMA_SQP_parse()" function
  652.  
  653.    /**
  654.     * Checks for token types being what we want...
  655.     *
  656.     * @param  string String of type that we have
  657.     * @param  string String of type that we want
  658.     *
  659.     * @return boolean result of check
  660.     *
  661.     * @access private
  662.     */
  663.     function PMA_SQP_typeCheck($toCheck, $whatWeWant)
  664.     {
  665.         $typeSeperator = '_';
  666.         if (strcmp($whatWeWant, $toCheck) == 0) {
  667.             return TRUE;
  668.         } else {
  669.             if (strpos($whatWeWant, $typeSeperator) === FALSE) {
  670.                 return strncmp($whatWeWant, $toCheck , strpos($toCheck, $typeSeperator)) == 0;
  671.             } else {
  672.                 return FALSE;
  673.             }
  674.         }
  675.     }
  676.  
  677.  
  678.     /**
  679.      * Analyzes SQL queries
  680.      *
  681.      * @param  array   The SQL queries
  682.      *
  683.      * @return array   The analyzed SQL queries
  684.      *
  685.      * @access public
  686.      */
  687.     function PMA_SQP_analyze($arr)
  688.     {
  689.         $result          = array();
  690.         $size            = $arr['len'];
  691.         $subresult       = array(
  692.             'querytype'      => '',
  693.             'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
  694.             'position_of_first_select' => '', // the array index
  695.             'from_clause'=> '',
  696.             'group_by_clause'=> '',
  697.             'order_by_clause'=> '',
  698.             'having_clause'  => '',
  699.             'where_clause'   => '',
  700.             'where_clause_identifiers'   => array(),
  701.             'unsorted_query' => '',
  702.             'queryflags'     => array(),
  703.             'select_expr'    => array(),
  704.             'table_ref'      => array(),
  705.             'foreign_keys'   => array(),
  706.             'create_table_fields' => array()
  707.         );
  708.         $subresult_empty = $subresult;
  709.         $seek_queryend         = FALSE;
  710.         $seen_end_of_table_ref = FALSE;
  711.  
  712.         // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
  713.         // we must not use CURDATE as a table_ref
  714.         // so we track wether we are in the EXTRACT()
  715.         $in_extract          = FALSE;
  716.  
  717.         // for GROUP_CONCAT( ... )
  718.         $in_group_concat     = FALSE;
  719.  
  720. /* Description of analyzer results by lem9
  721.  *
  722.  * db, table, column, alias
  723.  * ------------------------
  724.  *
  725.  * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
  726.  *
  727.  * The SELECT syntax (simplified) is
  728.  *
  729.  * SELECT
  730.  *    select_expression,...
  731.  *    [FROM [table_references]
  732.  *
  733.  *
  734.  * ['select_expr'] is filled with each expression, the key represents the
  735.  * expression position in the list (0-based) (so we don't lose track of
  736.  * multiple occurences of the same column).
  737.  *
  738.  * ['table_ref'] is filled with each table ref, same thing for the key.
  739.  *
  740.  * I create all sub-values empty, even if they are
  741.  * not present (for example no select_expression alias).
  742.  *
  743.  * There is a debug section at the end of loop #1, if you want to
  744.  * see the exact contents of select_expr and table_ref
  745.  *
  746.  * queryflags
  747.  * ----------
  748.  *
  749.  * In $subresult, array 'queryflags' is filled, according to what we
  750.  * find in the query.
  751.  *
  752.  * Currently, those are generated:
  753.  *
  754.  * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
  755.  * ['queryflags']['select_from'] = 1;  if this is a real SELECT...FROM
  756.  * ['queryflags']['distinct'] = 1;     for a DISTINCT
  757.  * ['queryflags']['union'] = 1;        for a UNION
  758.  * ['queryflags']['join'] = 1;         for a JOIN
  759.  * ['queryflags']['offset'] = 1;       for the presence of OFFSET 
  760.  *
  761.  * query clauses
  762.  * -------------
  763.  *
  764.  * The select is splitted in those clauses:
  765.  * ['select_expr_clause']
  766.  * ['from_clause']
  767.  * ['group_by_clause']
  768.  * ['order_by_clause']
  769.  * ['having_clause']
  770.  * ['where_clause']
  771.  *
  772.  * The identifiers of the WHERE clause are put into the array
  773.  * ['where_clause_identifier']
  774.  *
  775.  * For a SELECT, the whole query without the ORDER BY clause is put into
  776.  * ['unsorted_query']
  777.  *
  778.  * foreign keys
  779.  * ------------
  780.  * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
  781.  * analyzed and ['foreign_keys'] is an array filled with
  782.  * the constraint name, the index list,
  783.  * the REFERENCES table name and REFERENCES index list,
  784.  * and ON UPDATE | ON DELETE clauses
  785.  *
  786.  * position_of_first_select
  787.  * ------------------------
  788.  *
  789.  * The array index of the first SELECT we find. Will be used to
  790.  * insert a SQL_CALC_FOUND_ROWS.
  791.  *
  792.  * create_table_fields
  793.  * -------------------
  794.  *
  795.  * For now, mostly used to detect the DEFAULT CURRENT_TIMESTAMP and
  796.  * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
  797.  * An array, each element is the identifier name.
  798.  * Sub-elements: ['type'] which contains the column type
  799.  *               optional (currently they are never false but can be absent):
  800.  *               ['default_current_timestamp'] boolean
  801.  *               ['on_update_current_timestamp'] boolean
  802.  *
  803.  * End of description of analyzer results
  804.  */
  805.  
  806.         // must be sorted
  807.         // TODO: current logic checks for only one word, so I put only the
  808.         // first word of the reserved expressions that end a table ref;
  809.         // maybe this is not ok (the first word might mean something else)
  810. //        $words_ending_table_ref = array(
  811. //            'FOR UPDATE',
  812. //            'GROUP BY',
  813. //            'HAVING',
  814. //            'LIMIT',
  815. //            'LOCK IN SHARE MODE',
  816. //            'ORDER BY',
  817. //            'PROCEDURE',
  818. //            'UNION',
  819. //            'WHERE'
  820. //        );
  821.         $words_ending_table_ref = array(
  822.             'FOR',
  823.             'GROUP',
  824.             'HAVING',
  825.             'LIMIT',
  826.             'LOCK',
  827.             'ORDER',
  828.             'PROCEDURE',
  829.             'UNION',
  830.             'WHERE'
  831.         );
  832.         $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
  833.  
  834.         $words_ending_clauses = array(
  835.             'FOR',
  836.             'LIMIT',
  837.             'LOCK',
  838.             'PROCEDURE',
  839.             'UNION'
  840.         );
  841.         $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
  842.  
  843.  
  844.  
  845.  
  846.         // must be sorted
  847.         $supported_query_types = array(
  848.             'SELECT'
  849.             /*
  850.             // Support for these additional query types will come later on.
  851.             'DELETE',
  852.             'INSERT',
  853.             'REPLACE',
  854.             'TRUNCATE',
  855.             'UPDATE'
  856.             'EXPLAIN',
  857.             'DESCRIBE',
  858.             'SHOW',
  859.             'CREATE',
  860.             'SET',
  861.             'ALTER'
  862.             */
  863.         );
  864.         $supported_query_types_cnt = count($supported_query_types);
  865.  
  866.         // loop #1 for each token: select_expr, table_ref for SELECT
  867.  
  868.         for ($i = 0; $i < $size; $i++) {
  869. //DEBUG echo "trace loop1 <b>"  . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
  870.  
  871.             // High speed seek for locating the end of the current query
  872.             if ($seek_queryend == TRUE) {
  873.                 if ($arr[$i]['type'] == 'punct_queryend') {
  874.                     $seek_queryend = FALSE;
  875.                 } else {
  876.                     continue;
  877.                 } // end if (type == punct_queryend)
  878.             } // end if ($seek_queryend)
  879.  
  880.             // TODO: when we find a UNION, should we split
  881.             // in another subresult?
  882.             if ($arr[$i]['type'] == 'punct_queryend') {
  883.                 $result[]  = $subresult;
  884.                 $subresult = $subresult_empty;
  885.                 continue;
  886.             } // end if (type == punct_queryend)
  887.  
  888. // ==============================================================
  889.             if ($arr[$i]['type'] == 'punct_bracket_open_round') {
  890.                 if ($in_extract) {
  891.                     $number_of_brackets_in_extract++;
  892.                 }
  893.                 if ($in_group_concat) {
  894.                     $number_of_brackets_in_group_concat++;
  895.                 }
  896.             }
  897. // ==============================================================
  898.             if ($arr[$i]['type'] == 'punct_bracket_close_round') {
  899.                 if ($in_extract) {
  900.                     $number_of_brackets_in_extract--;
  901.                     if ($number_of_brackets_in_extract == 0) {
  902.                        $in_extract = FALSE;
  903.                     }
  904.                 }
  905.                 if ($in_group_concat) {
  906.                     $number_of_brackets_in_group_concat--;
  907.                     if ($number_of_brackets_in_group_concat == 0) {
  908.                        $in_group_concat = FALSE;
  909.                     }
  910.                 }
  911.             }
  912. // ==============================================================
  913.             if ($arr[$i]['type'] == 'alpha_functionName') {
  914.                 $upper_data = strtoupper($arr[$i]['data']);
  915.                 if ($upper_data =='EXTRACT') {
  916.                     $in_extract = TRUE;
  917.                     $number_of_brackets_in_extract = 0;
  918.                 }
  919.                 if ($upper_data =='GROUP_CONCAT') {
  920.                     $in_group_concat = TRUE;
  921.                     $number_of_brackets_in_group_concat = 0;
  922.                 }
  923.             }
  924.  
  925. // ==============================================================
  926.             if ($arr[$i]['type'] == 'alpha_reservedWord') {
  927.                 // We don't know what type of query yet, so run this
  928.                 if ($subresult['querytype'] == '') {
  929.                     $subresult['querytype'] = strtoupper($arr[$i]['data']);
  930.                 } // end if (querytype was empty)
  931.  
  932.                 // Check if we support this type of query
  933.                 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
  934.                     // Skip ahead to the next one if we don't
  935.                     $seek_queryend = TRUE;
  936.                     continue;
  937.                 } // end if (query not supported)
  938.  
  939.                 // upper once
  940.                 $upper_data = strtoupper($arr[$i]['data']);
  941.                 //TODO: reset for each query?
  942.  
  943.                 if ($upper_data == 'SELECT') {
  944.                     $seen_from = FALSE;
  945.                     $previous_was_identifier = FALSE;
  946.                     $current_select_expr = -1;
  947.                     $seen_end_of_table_ref = FALSE;
  948.                 } // end if ( data == SELECT)
  949.  
  950.                 if ($upper_data =='FROM' && !$in_extract) {
  951.                     $current_table_ref = -1;
  952.                     $seen_from = TRUE;
  953.                     $previous_was_identifier = FALSE;
  954.                     $save_table_ref = TRUE;
  955.                 } // end if (data == FROM)
  956.  
  957.                 // here, do not 'continue' the loop, as we have more work for
  958.                 // reserved words below
  959.             } // end if (type == alpha_reservedWord)
  960.  
  961. // ==============================
  962.             if (($arr[$i]['type'] == 'quote_backtick')
  963.              || ($arr[$i]['type'] == 'quote_double')
  964.              || ($arr[$i]['type'] == 'quote_single')
  965.              || ($arr[$i]['type'] == 'alpha_identifier')) {
  966.  
  967.                 switch ($arr[$i]['type']) {
  968.                     case 'alpha_identifier':
  969.                         $identifier = $arr[$i]['data'];
  970.                         break;
  971.  
  972.                 //TODO: check embedded double quotes or backticks?
  973.                 // and/or remove just the first and last character?
  974.                     case 'quote_backtick':
  975.                         $identifier = str_replace('`','',$arr[$i]['data']);
  976.                         break;
  977.                     case 'quote_double':
  978.                         $identifier = str_replace('"','',$arr[$i]['data']);
  979.                         break;
  980.                     case 'quote_single':
  981.                         $identifier = str_replace("'","",$arr[$i]['data']);
  982.                         break;
  983.                 } // end switch
  984.  
  985.                 if ($subresult['querytype'] == 'SELECT' && !$in_group_concat) {
  986.                     if (!$seen_from) {
  987.                         if ($previous_was_identifier && isset($chain)) {
  988.                             // found alias for this select_expr, save it
  989.                             // but only if we got something in $chain
  990.                             // (for example, SELECT COUNT(*) AS cnt
  991.                             // puts nothing in $chain, so we avoid
  992.                             // setting the alias)
  993.                             $alias_for_select_expr = $identifier;
  994.                         } else {
  995.                             $chain[] = $identifier;
  996.                             $previous_was_identifier = TRUE;
  997.  
  998.                         } // end if !$previous_was_identifier
  999.                     } else {
  1000.                         // ($seen_from)
  1001.                         if ($save_table_ref && !$seen_end_of_table_ref) {
  1002.                             if ($previous_was_identifier) {
  1003.                                 // found alias for table ref
  1004.                                 // save it for later
  1005.                                 $alias_for_table_ref = $identifier;
  1006.                             } else {
  1007.                                 $chain[] = $identifier;
  1008.                                 $previous_was_identifier = TRUE;
  1009.  
  1010.                             } // end if ($previous_was_identifier)
  1011.                         } // end if ($save_table_ref &&!$seen_end_of_table_ref)
  1012.                     } // end if (!$seen_from)
  1013.                 } // end if (querytype SELECT)
  1014.             } // end if ( quote_backtick or double quote or alpha_identifier)
  1015.  
  1016. // ===================================
  1017.             if ($arr[$i]['type'] == 'punct_qualifier') {
  1018.                 // to be able to detect an identifier following another
  1019.                 $previous_was_identifier = FALSE;
  1020.                 continue;
  1021.             } // end if (punct_qualifier)
  1022.  
  1023.             // TODO: check if 3 identifiers following one another -> error
  1024.  
  1025.             //    s a v e    a    s e l e c t    e x p r
  1026.             // finding a list separator or FROM
  1027.             // means that we must save the current chain of identifiers
  1028.             // into a select expression
  1029.  
  1030.             // for now, we only save a select expression if it contains
  1031.             // at least one identifier, as we are interested in checking
  1032.             // the columns and table names, so in "select * from persons",
  1033.             // the "*" is not saved
  1034.  
  1035.             if (isset($chain) && !$seen_end_of_table_ref
  1036.                && (   (!$seen_from
  1037.                    && $arr[$i]['type'] == 'punct_listsep')
  1038.                   || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM')) ) {
  1039.                 $size_chain = count($chain);
  1040.                 $current_select_expr++;
  1041.                 $subresult['select_expr'][$current_select_expr] = array(
  1042.                   'expr' => '',
  1043.                   'alias' => '',
  1044.                   'db'   => '',
  1045.                   'table_name' => '',
  1046.                   'table_true_name' => '',
  1047.                   'column' => ''
  1048.                  );
  1049.  
  1050.                 if (!empty($alias_for_select_expr)) {
  1051.                     // we had found an alias for this select expression
  1052.                     $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
  1053.                     unset($alias_for_select_expr);
  1054.                 }
  1055.                 // there is at least a column
  1056.                 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
  1057.                 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
  1058.  
  1059.                 // maybe a table
  1060.                 if ($size_chain > 1) {
  1061.                     $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
  1062.                     // we assume for now that this is also the true name
  1063.                     $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
  1064.                     $subresult['select_expr'][$current_select_expr]['expr']
  1065.                      = $subresult['select_expr'][$current_select_expr]['table_name']
  1066.                       . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
  1067.                 } // end if ($size_chain > 1)
  1068.  
  1069.                 // maybe a db
  1070.                 if ($size_chain > 2) {
  1071.                     $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
  1072.                     $subresult['select_expr'][$current_select_expr]['expr']
  1073.                      = $subresult['select_expr'][$current_select_expr]['db']
  1074.                       . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
  1075.                 } // end if ($size_chain > 2)
  1076.                 unset($chain);
  1077.  
  1078.                 // TODO: explain this:
  1079.                 if (($arr[$i]['type'] == 'alpha_reservedWord')
  1080.                  && ($upper_data != 'FROM')) {
  1081.                     $previous_was_identifier = TRUE;
  1082.                 }
  1083.  
  1084.             } // end if (save a select expr)
  1085.  
  1086.  
  1087.             //======================================
  1088.             //    s a v e    a    t a b l e    r e f
  1089.             //======================================
  1090.  
  1091.             // maybe we just saw the end of table refs
  1092.             // but the last table ref has to be saved
  1093.             // or we are at the last token (TODO: there could be another
  1094.             // query after this one)
  1095.             // or we just got a reserved word
  1096.  
  1097.             if (isset($chain) && $seen_from && $save_table_ref
  1098.              && ($arr[$i]['type'] == 'punct_listsep'
  1099.                || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
  1100.                || $seen_end_of_table_ref
  1101.                || $i==$size-1 )) {
  1102.  
  1103.                 $size_chain = count($chain);
  1104.                 $current_table_ref++;
  1105.                 $subresult['table_ref'][$current_table_ref] = array(
  1106.                   'expr'            => '',
  1107.                   'db'              => '',
  1108.                   'table_name'      => '',
  1109.                   'table_alias'     => '',
  1110.                   'table_true_name' => ''
  1111.                  );
  1112.                 if (!empty($alias_for_table_ref)) {
  1113.                     $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
  1114.                     unset($alias_for_table_ref);
  1115.                 }
  1116.                 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
  1117.                 // we assume for now that this is also the true name
  1118.                 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
  1119.                 $subresult['table_ref'][$current_table_ref]['expr']
  1120.                      = $subresult['table_ref'][$current_table_ref]['table_name'];
  1121.                 // maybe a db
  1122.                 if ($size_chain > 1) {
  1123.                     $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
  1124.                     $subresult['table_ref'][$current_table_ref]['expr']
  1125.                      = $subresult['table_ref'][$current_table_ref]['db']
  1126.                       . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
  1127.                 } // end if ($size_chain > 1)
  1128.  
  1129.                 // add the table alias into the whole expression
  1130.                 $subresult['table_ref'][$current_table_ref]['expr']
  1131.                  .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
  1132.  
  1133.                 unset($chain);
  1134.                 $previous_was_identifier = TRUE;
  1135.                 //continue;
  1136.  
  1137.             } // end if (save a table ref)
  1138.  
  1139.  
  1140.             // when we have found all table refs,
  1141.             // for each table_ref alias, put the true name of the table
  1142.             // in the corresponding select expressions
  1143.  
  1144.             if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1)) {
  1145.                 for ($tr=0; $tr <= $current_table_ref; $tr++) {
  1146.                     $alias = $subresult['table_ref'][$tr]['table_alias'];
  1147.                     $truename = $subresult['table_ref'][$tr]['table_true_name'];
  1148.                     for ($se=0; $se <= $current_select_expr; $se++) {
  1149.                         if (!empty($alias) && $subresult['select_expr'][$se]['table_true_name']
  1150.                            == $alias) {
  1151.                             $subresult['select_expr'][$se]['table_true_name']
  1152.                              = $truename;
  1153.                         } // end if (found the alias)
  1154.                     } // end for (select expressions)
  1155.  
  1156.                 } // end for (table refs)
  1157.             } // end if (set the true names)
  1158.  
  1159.  
  1160.            // e n d i n g    l o o p  #1
  1161.            // set the $previous_was_identifier to FALSE if the current
  1162.            // token is not an identifier
  1163.            if (($arr[$i]['type'] != 'alpha_identifier')
  1164.             && ($arr[$i]['type'] != 'quote_double')
  1165.             && ($arr[$i]['type'] != 'quote_single')
  1166.             && ($arr[$i]['type'] != 'quote_backtick')) {
  1167.                $previous_was_identifier = FALSE;
  1168.            } // end if
  1169.  
  1170.            // however, if we are on AS, we must keep the $previous_was_identifier
  1171.            if (($arr[$i]['type'] == 'alpha_reservedWord')
  1172.             && ($upper_data == 'AS'))  {
  1173.                $previous_was_identifier = TRUE;
  1174.            }
  1175.  
  1176.            if (($arr[$i]['type'] == 'alpha_reservedWord')
  1177.             && ($upper_data =='ON' || $upper_data =='USING')) {
  1178.                $save_table_ref = FALSE;
  1179.            } // end if (data == ON)
  1180.  
  1181.            if (($arr[$i]['type'] == 'alpha_reservedWord')
  1182.             && ($upper_data =='JOIN' || $upper_data =='FROM')) {
  1183.                $save_table_ref = TRUE;
  1184.            } // end if (data == JOIN)
  1185.  
  1186.            // no need to check the end of table ref if we already did
  1187.            // TODO: maybe add "&& $seen_from"
  1188.            if (!$seen_end_of_table_ref) {
  1189.                // if this is the last token, it implies that we have
  1190.                // seen the end of table references
  1191.                // Check for the end of table references
  1192.                //
  1193.                // Note: if we are analyzing a GROUP_CONCAT clause,
  1194.                // we might find a word that seems to indicate that
  1195.                // we have found the end of table refs (like ORDER)
  1196.                // but it's a modifier of the GROUP_CONCAT so
  1197.                // it's not the real end of table refs
  1198.                if (($i == $size-1)
  1199.                || ($arr[$i]['type'] == 'alpha_reservedWord'
  1200.                   && !$in_group_concat
  1201.                   && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
  1202.                    $seen_end_of_table_ref = TRUE;
  1203.                    // to be able to save the last table ref, but do not
  1204.                    // set it true if we found a word like "ON" that has
  1205.                    // already set it to false
  1206.                    if (isset($save_table_ref) && $save_table_ref != FALSE) {
  1207.                       $save_table_ref = TRUE;
  1208.                    } //end if
  1209.  
  1210.                } // end if (check for end of table ref)
  1211.            } //end if (!$seen_end_of_table_ref)
  1212.  
  1213.            if ($seen_end_of_table_ref) {
  1214.                $save_table_ref = FALSE;
  1215.            } // end if
  1216.  
  1217.         } // end for $i (loop #1)
  1218.  
  1219.         // -------------------------------------------------------
  1220.         // This is a big hunk of debugging code by Marc for this.
  1221.         // -------------------------------------------------------
  1222.         /*
  1223.           if (isset($current_select_expr)) {
  1224.            for ($trace=0; $trace<=$current_select_expr; $trace++) {
  1225.                echo "<br />";
  1226.                reset ($subresult['select_expr'][$trace]);
  1227.                while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
  1228.                    echo "sel expr $trace $key => $val<br />\n";
  1229.                }
  1230.           }
  1231.  
  1232.           if (isset($current_table_ref)) {
  1233.            echo "current_table_ref = " . $current_table_ref . "<br>";
  1234.            for ($trace=0; $trace<=$current_table_ref; $trace++) {
  1235.  
  1236.                echo "<br />";
  1237.                reset ($subresult['table_ref'][$trace]);
  1238.                while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
  1239.                echo "table ref $trace $key => $val<br />\n";
  1240.                }
  1241.           }
  1242.         */
  1243.         // -------------------------------------------------------
  1244.  
  1245.  
  1246.         // loop #2: for queryflags
  1247.         //          ,querytype (for queries != 'SELECT')
  1248.         //
  1249.         // we will also need this queryflag in loop 2
  1250.         // so set it here
  1251.         if (isset($current_table_ref) && $current_table_ref > -1) {
  1252.             $subresult['queryflags']['select_from'] = 1;
  1253.         }
  1254.  
  1255.         $seen_reserved_word = FALSE;
  1256.         $seen_group = FALSE;
  1257.         $seen_order = FALSE;
  1258.         $in_group_by = FALSE; // true when we are inside the GROUP BY clause
  1259.         $in_order_by = FALSE; // true when we are inside the ORDER BY clause
  1260.         $in_having = FALSE; // true when we are inside the HAVING clause
  1261.         $in_select_expr = FALSE; // true when we are inside the select expr clause
  1262.         $in_where = FALSE; // true when we are inside the WHERE clause
  1263.         $in_from = FALSE;
  1264.         $in_group_concat = FALSE;
  1265.         $unsorted_query = '';
  1266.  
  1267.         for ($i = 0; $i < $size; $i++) {
  1268. //DEBUG echo "trace loop2 <b>"  . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
  1269.  
  1270.            // need_confirm
  1271.            //
  1272.            // check for reserved words that will have to generate
  1273.            // a confirmation request later in sql.php
  1274.            // the cases are:
  1275.            //   DROP TABLE
  1276.            //   DROP DATABASE
  1277.            //   ALTER TABLE... DROP
  1278.            //   DELETE FROM...
  1279.            //
  1280.            // this code is not used for confirmations coming from functions.js
  1281.  
  1282.            // TODO: check for punct_queryend
  1283.  
  1284.            if ($arr[$i]['type'] == 'alpha_reservedWord') {
  1285.                $upper_data = strtoupper($arr[$i]['data']);
  1286.                if (!$seen_reserved_word) {
  1287.                    $first_reserved_word = $upper_data;
  1288.                    $subresult['querytype'] = $upper_data;
  1289.                    $seen_reserved_word = TRUE;
  1290.  
  1291.                    // if the first reserved word is DROP or DELETE,
  1292.                    // we know this is a query that needs to be confirmed
  1293.                    if ($first_reserved_word=='DROP'
  1294.                        || $first_reserved_word == 'DELETE'
  1295.                        || $first_reserved_word == 'TRUNCATE') {
  1296.                       $subresult['queryflags']['need_confirm'] = 1;
  1297.                    }
  1298.  
  1299.                    if ($first_reserved_word=='SELECT'){
  1300.                        $position_of_first_select = $i;
  1301.                    }
  1302.  
  1303.                } else {
  1304.                    if ($upper_data=='DROP' && $first_reserved_word=='ALTER') {
  1305.                       $subresult['queryflags']['need_confirm'] = 1;
  1306.                    }
  1307.                }
  1308.  
  1309.                if ($upper_data == 'SELECT') {
  1310.                    $in_select_expr = TRUE;
  1311.                    $select_expr_clause = '';
  1312.                }
  1313.                if ($upper_data == 'DISTINCT' && !$in_group_concat) {
  1314.                       $subresult['queryflags']['distinct'] = 1;
  1315.                }
  1316.  
  1317.                if ($upper_data == 'UNION') {
  1318.                       $subresult['queryflags']['union'] = 1;
  1319.                }
  1320.  
  1321.                if ($upper_data == 'JOIN') {
  1322.                       $subresult['queryflags']['join'] = 1;
  1323.                }
  1324.  
  1325.                if ($upper_data == 'OFFSET') {
  1326.                       $subresult['queryflags']['offset'] = 1;
  1327.                }
  1328.  
  1329.                // if this is a real SELECT...FROM
  1330.                if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
  1331.                    $in_from = TRUE;
  1332.                    $from_clause = '';
  1333.                    $in_select_expr = FALSE;
  1334.                }
  1335.  
  1336.  
  1337.                // (we could have less resetting of variables to FALSE
  1338.                // if we trust that the query respects the standard
  1339.                // MySQL order for clauses)
  1340.  
  1341.                // we use $seen_group and $seen_order because we are looking
  1342.                // for the BY
  1343.                if ($upper_data == 'GROUP') {
  1344.                    $seen_group = TRUE;
  1345.                    $seen_order = FALSE;
  1346.                    $in_having = FALSE;
  1347.                    $in_order_by = FALSE;
  1348.                    $in_where = FALSE;
  1349.                    $in_select_expr = FALSE;
  1350.                    $in_from = FALSE;
  1351.                }
  1352.                if ($upper_data == 'ORDER' && !$in_group_concat) {
  1353.                    $seen_order = TRUE;
  1354.                    $seen_group = FALSE;
  1355.                    $in_having = FALSE;
  1356.                    $in_group_by = FALSE;
  1357.                    $in_where = FALSE;
  1358.                    $in_select_expr = FALSE;
  1359.                    $in_from = FALSE;
  1360.                }
  1361.                if ($upper_data == 'HAVING') {
  1362.                    $in_having = TRUE;
  1363.                    $having_clause = '';
  1364.                    $seen_group = FALSE;
  1365.                    $seen_order = FALSE;
  1366.                    $in_group_by = FALSE;
  1367.                    $in_order_by = FALSE;
  1368.                    $in_where = FALSE;
  1369.                    $in_select_expr = FALSE;
  1370.                    $in_from = FALSE;
  1371.                }
  1372.  
  1373.                if ($upper_data == 'WHERE') {
  1374.                    $in_where = TRUE;
  1375.                    $where_clause = '';
  1376.                    $where_clause_identifiers = array();
  1377.                    $seen_group = FALSE;
  1378.                    $seen_order = FALSE;
  1379.                    $in_group_by = FALSE;
  1380.                    $in_order_by = FALSE;
  1381.                    $in_having = FALSE;
  1382.                    $in_select_expr = FALSE;
  1383.                    $in_from = FALSE;
  1384.                }
  1385.  
  1386.                if ($upper_data == 'BY') {
  1387.                    if ($seen_group) {
  1388.                        $in_group_by = TRUE;
  1389.                        $group_by_clause = '';
  1390.                    }
  1391.                    if ($seen_order) {
  1392.                        $in_order_by = TRUE;
  1393.                        $order_by_clause = '';
  1394.                    }
  1395.                }
  1396.  
  1397.                // if we find one of the words that could end the clause
  1398.                if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
  1399.  
  1400.                    $in_group_by = FALSE;
  1401.                    $in_order_by = FALSE;
  1402.                    $in_having   = FALSE;
  1403.                    $in_where    = FALSE;
  1404.                    $in_select_expr = FALSE;
  1405.                    $in_from = FALSE;
  1406.                }
  1407.  
  1408.            } // endif (reservedWord)
  1409.  
  1410.  
  1411.            // do not add a blank after a function name
  1412.            // TODO: can we combine loop 2 and loop 1?
  1413.            // some code is repeated here...
  1414.  
  1415.            $sep=' ';
  1416.            if ($arr[$i]['type'] == 'alpha_functionName') {
  1417.                $sep='';
  1418.                $upper_data = strtoupper($arr[$i]['data']);
  1419.                if ($upper_data =='GROUP_CONCAT') {
  1420.                    $in_group_concat = TRUE;
  1421.                    $number_of_brackets_in_group_concat = 0;
  1422.                }
  1423.            }
  1424.  
  1425.            if ($arr[$i]['type'] == 'punct_bracket_open_round') {
  1426.                if ($in_group_concat) {
  1427.                   $number_of_brackets_in_group_concat++;
  1428.                }
  1429.            }
  1430.            if ($arr[$i]['type'] == 'punct_bracket_close_round') {
  1431.                if ($in_group_concat) {
  1432.                   $number_of_brackets_in_group_concat--;
  1433.                   if ($number_of_brackets_in_group_concat == 0) {
  1434.                      $in_group_concat = FALSE;
  1435.                   }
  1436.                }
  1437.            }
  1438.  
  1439.            if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
  1440.                $select_expr_clause .= $arr[$i]['data'] . $sep;
  1441.            }
  1442.            if ($in_from && $upper_data != 'FROM') {
  1443.                $from_clause .= $arr[$i]['data'] . $sep;
  1444.            }
  1445.            if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
  1446.                $group_by_clause .= $arr[$i]['data'] . $sep;
  1447.            }
  1448.            if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
  1449.                $order_by_clause .= $arr[$i]['data'] . $sep;
  1450.            }
  1451.            if ($in_having && $upper_data != 'HAVING') {
  1452.                $having_clause .= $arr[$i]['data'] . $sep;
  1453.            }
  1454.            if ($in_where && $upper_data != 'WHERE') {
  1455.                $where_clause .= $arr[$i]['data'] . $sep;
  1456.  
  1457.                if (($arr[$i]['type'] == 'quote_backtick')
  1458.                 || ($arr[$i]['type'] == 'alpha_identifier')) {
  1459.                    $where_clause_identifiers[] = $arr[$i]['data'];
  1460.                }
  1461.            }
  1462.  
  1463.            // FIXME: is it correct to always add $sep ?
  1464.            if (isset($subresult['queryflags']['select_from'])
  1465.              && $subresult['queryflags']['select_from'] == 1
  1466.              && !$seen_order) {
  1467.                $unsorted_query .= $arr[$i]['data'] . $sep;
  1468.            }
  1469.  
  1470.            // clear $upper_data for next iteration
  1471.            $upper_data='';
  1472.  
  1473.         } // end for $i (loop #2)
  1474.  
  1475.         // -----------------------------------------------------
  1476.         // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
  1477.         // (for now, check only the first query)
  1478.         // (for now, identifiers are assumed to be backquoted)
  1479.  
  1480.         // If we find that we are dealing with a CREATE TABLE query,
  1481.         // we look for the next punct_bracket_open_round, which 
  1482.         // introduces the fields list. Then, when we find a
  1483.         // quote_backtick, it must be a field, so we put it into
  1484.         // the create_table_fields array. Even if this field is
  1485.         // not a timestamp, it will be useful when logic has been
  1486.         // added for complete field attributes analysis.
  1487.  
  1488.         $seen_foreign = FALSE;
  1489.         $seen_references = FALSE;
  1490.         $seen_constraint = FALSE;
  1491.         $foreign_key_number = -1;
  1492.         $seen_create_table = FALSE;
  1493.         $seen_create = FALSE;
  1494.         $in_create_table_fields = FALSE;
  1495.         $brackets_level = 0;
  1496.         $in_timestamp_options = FALSE;
  1497.         $seen_default = FALSE;
  1498.  
  1499.         for ($i = 0; $i < $size; $i++) {
  1500.         // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
  1501.  
  1502.             if ($arr[$i]['type'] == 'alpha_reservedWord') {
  1503.                 $upper_data = strtoupper($arr[$i]['data']);
  1504.  
  1505.                 if ($upper_data == 'CREATE') {
  1506.                     $seen_create = TRUE;
  1507.                 }
  1508.  
  1509.                 if ($upper_data == 'TABLE' && $seen_create) {
  1510.                     $seen_create_table = TRUE;
  1511.                     $create_table_fields = array();
  1512.                 }
  1513.  
  1514.                 if ($upper_data == 'CURRENT_TIMESTAMP') {
  1515.                     if ($in_timestamp_options) {
  1516.                         if ($seen_default) {
  1517.                             $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
  1518.                         }
  1519.                     }
  1520.                 }
  1521.  
  1522.                 if ($upper_data == 'CONSTRAINT') {
  1523.                     $foreign_key_number++;
  1524.                     $seen_foreign = FALSE;
  1525.                     $seen_references = FALSE;
  1526.                     $seen_constraint = TRUE;
  1527.                 }
  1528.                 if ($upper_data == 'FOREIGN') {
  1529.                     $seen_foreign = TRUE;
  1530.                     $seen_references = FALSE;
  1531.                     $seen_constraint = FALSE;
  1532.                 }
  1533.                 if ($upper_data == 'REFERENCES') {
  1534.                     $seen_foreign = FALSE;
  1535.                     $seen_references = TRUE;
  1536.                     $seen_constraint = FALSE;
  1537.                 }
  1538.  
  1539.  
  1540.               // Cases covered:
  1541.  
  1542.               // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
  1543.               // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
  1544.  
  1545.               // but we set ['on_delete'] or ['on_cascade'] to
  1546.               // CASCADE | SET_NULL | NO_ACTION | RESTRICT
  1547.  
  1548.               // ON UPDATE CURRENT_TIMESTAMP
  1549.  
  1550.                if ($upper_data == 'ON') {
  1551.                    unset($clause);
  1552.                    if ($arr[$i+1]['type'] == 'alpha_reservedWord') {
  1553.                        $second_upper_data = strtoupper($arr[$i+1]['data']);
  1554.                        if ($second_upper_data == 'DELETE') {
  1555.                             $clause = 'on_delete';
  1556.                        }
  1557.                        if ($second_upper_data == 'UPDATE') {
  1558.                             $clause = 'on_update';
  1559.                        }
  1560.                        if (isset($clause)
  1561.                        && ($arr[$i+2]['type'] == 'alpha_reservedWord'
  1562.  
  1563.              // ugly workaround because currently, NO is not
  1564.              // in the list of reserved words in sqlparser.data
  1565.              // (we got a bug report about not being able to use
  1566.              // 'no' as an identifier)
  1567.                            || ($arr[$i+2]['type'] == 'alpha_identifier'
  1568.                               && strtoupper($arr[$i+2]['data'])=='NO') )
  1569.                           ) {
  1570.                           $third_upper_data = strtoupper($arr[$i+2]['data']);
  1571.                           if ($third_upper_data == 'CASCADE'
  1572.                            || $third_upper_data == 'RESTRICT') {
  1573.                               $value = $third_upper_data;
  1574.                           } elseif ($third_upper_data == 'SET'
  1575.                                  || $third_upper_data == 'NO') {
  1576.                               if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
  1577.                                   $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
  1578.                               }
  1579.                           } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
  1580.                               if ($clause == 'on_update' 
  1581.                                   && $in_timestamp_options) {
  1582.                                   $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
  1583.                                   $seen_default = FALSE;
  1584.                               }
  1585.                               
  1586.                           } else {
  1587.                               $value = '';
  1588.                           }
  1589.                           if (!empty($value)) {
  1590.                               $foreign[$foreign_key_number][$clause] = $value;
  1591.                           }
  1592.                        }
  1593.                    }
  1594.                }
  1595.  
  1596.             } // end of reserved words analysis
  1597.             
  1598.  
  1599.             if ($arr[$i]['type'] == 'punct_bracket_open_round') {
  1600.                 $brackets_level++;
  1601.                 if ($seen_create_table && $brackets_level == 1) {
  1602.                     $in_create_table_fields = TRUE;
  1603.                 }
  1604.             }
  1605.  
  1606.             
  1607.             if ($arr[$i]['type'] == 'punct_bracket_close_round') {
  1608.                 $brackets_level--;
  1609.                 if ($seen_references) {
  1610.                     $seen_references = FALSE;
  1611.                 }
  1612.                 if ($seen_create_table && $brackets_level == 0) {
  1613.                     $in_create_table_fields = FALSE;
  1614.                 }
  1615.             }
  1616.  
  1617.             
  1618.             if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
  1619.                 $upper_data = strtoupper($arr[$i]['data']);
  1620.                 if ($seen_create_table && $in_create_table_fields) {
  1621.                     if ($upper_data == 'DEFAULT') {
  1622.                         $seen_default = TRUE;
  1623.                     }
  1624.                 }
  1625.             }
  1626.  
  1627.  
  1628.             if (($arr[$i]['type'] == 'alpha_columnType')) {
  1629.                 $upper_data = strtoupper($arr[$i]['data']);
  1630.                 if ($seen_create_table && $in_create_table_fields) {
  1631.                     $create_table_fields[$current_identifier]['type'] = $upper_data;
  1632.                     if ($upper_data == 'TIMESTAMP') {
  1633.                         $in_timestamp_options = TRUE;
  1634.                     } else {
  1635.                         $in_timestamp_options = FALSE;
  1636.                     }
  1637.                 }
  1638.             }
  1639.  
  1640.             
  1641.             if (($arr[$i]['type'] == 'quote_backtick')) {
  1642.  
  1643.                 // TODO: one set of IFs to remove backquotes
  1644.  
  1645.                 if ($seen_create_table && $in_create_table_fields) {
  1646.                     // remove backquotes
  1647.                     $identifier = str_replace('`','',$arr[$i]['data']);
  1648.                     $current_identifier = $identifier;
  1649.                 }
  1650.  
  1651.                 if ($seen_constraint) {
  1652.                     // remove backquotes
  1653.                     $identifier = str_replace('`','',$arr[$i]['data']);
  1654.                     $foreign[$foreign_key_number]['constraint'] = $identifier;
  1655.                 }
  1656.                 if ($seen_foreign && $brackets_level > 0) {
  1657.                     // remove backquotes
  1658.                     $identifier = str_replace('`','',$arr[$i]['data']);
  1659.                     $foreign[$foreign_key_number]['index_list'][] = $identifier;
  1660.                 }
  1661.  
  1662.                 if ($seen_references) {
  1663.                     // remove backquotes
  1664.                     $identifier = str_replace('`','',$arr[$i]['data']);
  1665.                     // here, the first bracket level corresponds to the
  1666.                     // bracket of CREATE TABLE
  1667.                     // so if we are on level 2, it must be the index list
  1668.                     // of the foreign key REFERENCES
  1669.                     if ($brackets_level > 1) {
  1670.                         $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
  1671.                     } else {
  1672.                         // for MySQL 4.0.18, identifier is
  1673.                         // `table` or `db`.`table`
  1674.                         // first pass will pick the db name
  1675.                         // next pass will execute the else and pick the
  1676.                         // db name in $db_table[0]
  1677.                         if ($arr[$i+1]['type'] == 'punct_qualifier') {
  1678.                                 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
  1679.                         } else {
  1680.                         // for MySQL 4.0.16, identifier is
  1681.                         // `table` or `db.table`
  1682.                             $db_table = explode('.',$identifier);
  1683.                             if (isset($db_table[1])) {
  1684.                                 $foreign[$foreign_key_number]['ref_db_name'] = $db_table[0];
  1685.                                 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[1];
  1686.                             } else {
  1687.                                 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[0];
  1688.                             }
  1689.                         }
  1690.                     }
  1691.                 }
  1692.             }
  1693.         } // end for $i (loop #3)
  1694.  
  1695.  
  1696.         // Fill the $subresult array
  1697.  
  1698.         if (isset($create_table_fields)) {
  1699.             $subresult['create_table_fields'] = $create_table_fields;
  1700.         }
  1701.  
  1702.         if (isset($foreign)) {
  1703.             $subresult['foreign_keys'] = $foreign;
  1704.         }
  1705.  
  1706.         if (isset($select_expr_clause)) {
  1707.             $subresult['select_expr_clause'] = $select_expr_clause;
  1708.         }
  1709.         if (isset($from_clause)) {
  1710.             $subresult['from_clause'] = $from_clause;
  1711.         }
  1712.         if (isset($group_by_clause)) {
  1713.             $subresult['group_by_clause'] = $group_by_clause;
  1714.         }
  1715.         if (isset($order_by_clause)) {
  1716.             $subresult['order_by_clause'] = $order_by_clause;
  1717.         }
  1718.         if (isset($having_clause)) {
  1719.             $subresult['having_clause'] = $having_clause;
  1720.         }
  1721.         if (isset($where_clause)) {
  1722.             $subresult['where_clause'] = $where_clause;
  1723.         }
  1724.         if (isset($unsorted_query) && !empty($unsorted_query)) {
  1725.             $subresult['unsorted_query'] = $unsorted_query;
  1726.         }
  1727.         if (isset($where_clause_identifiers)) {
  1728.             $subresult['where_clause_identifiers'] = $where_clause_identifiers;
  1729.         }
  1730.  
  1731.         if (isset($position_of_first_select)) {
  1732.             $subresult['position_of_first_select'] = $position_of_first_select;
  1733.         }
  1734.  
  1735.         // They are naughty and didn't have a trailing semi-colon,
  1736.         // then still handle it properly
  1737.         if ($subresult['querytype'] != '') {
  1738.             $result[] = $subresult;
  1739.         }
  1740.         return $result;
  1741.     } // end of the "PMA_SQP_analyze()" function
  1742.  
  1743.  
  1744.     /**
  1745.      * Colorizes SQL queries html formatted
  1746.      *
  1747.      * @param  array   The SQL queries html formatted
  1748.      *
  1749.      * @return array   The colorized SQL queries
  1750.      *
  1751.      * @access public
  1752.      */
  1753.     function PMA_SQP_formatHtml_colorize($arr)
  1754.     {
  1755.         $i         = $GLOBALS['PMA_strpos']($arr['type'], '_');
  1756.         $class     = '';
  1757.         if ($i > 0) {
  1758.             $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
  1759.         }
  1760.  
  1761.         $class     .= 'syntax_' . $arr['type'];
  1762.  
  1763.         //TODO: check why adding a "\n" after the </span> would cause extra
  1764.         //      blanks to be displayed:
  1765.         //      SELECT p . person_name
  1766.  
  1767.         return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
  1768.     } // end of the "PMA_SQP_formatHtml_colorize()" function
  1769.  
  1770.  
  1771.     /**
  1772.      * Formats SQL queries to html
  1773.      *
  1774.      * @param  array   The SQL queries
  1775.      * @param  string  mode
  1776.      * @param  integer starting token
  1777.      * @param  integer number of tokens to format, -1 = all
  1778.      *
  1779.      * @return string  The formatted SQL queries
  1780.      *
  1781.      * @access public
  1782.      */
  1783.     function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
  1784.         $number_of_tokens=-1)
  1785.     {
  1786.         // then check for an array
  1787.         if (!is_array($arr)) {
  1788.             return htmlspecialchars($arr);
  1789.         }
  1790.         // first check for the SQL parser having hit an error
  1791.         if (PMA_SQP_isError()) {
  1792.             return htmlspecialchars($arr['raw']);
  1793.         }
  1794.         // else do it properly
  1795.         switch ($mode) {
  1796.             case 'color':
  1797.                 $str                                = '<span class="syntax">';
  1798.                 $html_line_break                    = '<br />';
  1799.                 break;
  1800.             case 'query_only':
  1801.                 $str                                = '';
  1802.                 $html_line_break                    = "\n";
  1803.                 break;
  1804.             case 'text':
  1805.                 $str                                = '';
  1806.                 $html_line_break                    = '<br />';
  1807.                 break;
  1808.         } // end switch
  1809.         $indent                                     = 0;
  1810.         $bracketlevel                               = 0;
  1811.         $functionlevel                              = 0;
  1812.         $infunction                                 = FALSE;
  1813.         $space_punct_listsep                        = ' ';
  1814.         $space_punct_listsep_function_name          = ' ';
  1815.         // $space_alpha_reserved_word = '<br />'."\n";
  1816.         $space_alpha_reserved_word                  = ' ';
  1817.  
  1818.         $keywords_with_brackets_1before            = array(
  1819.             'INDEX',
  1820.             'KEY',
  1821.             'ON',
  1822.             'USING'
  1823.         );
  1824.         $keywords_with_brackets_1before_cnt        = 4;
  1825.  
  1826.         $keywords_with_brackets_2before            = array(
  1827.             'IGNORE',
  1828.             'INDEX',
  1829.             'INTO',
  1830.             'KEY',
  1831.             'PRIMARY',
  1832.             'PROCEDURE',
  1833.             'REFERENCES',
  1834.             'UNIQUE',
  1835.             'USE'
  1836.         );
  1837.         // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
  1838.         $keywords_with_brackets_2before_cnt        = 9;
  1839.  
  1840.         // These reserved words do NOT get a newline placed near them.
  1841.         $keywords_no_newline               = array(
  1842.             'AS',
  1843.             'ASC',
  1844.             'DESC',
  1845.             'DISTINCT',
  1846.             'HOUR',
  1847.             'INTERVAL',
  1848.             'IS',
  1849.             'LIKE',
  1850.             'NOT',
  1851.             'NULL',
  1852.             'ON',
  1853.             'REGEXP'
  1854.         );
  1855.         $keywords_no_newline_cnt           = 12;
  1856.  
  1857.         // These reserved words introduce a privilege list
  1858.         $keywords_priv_list                = array(
  1859.             'GRANT',
  1860.             'REVOKE'
  1861.         );
  1862.         $keywords_priv_list_cnt            = 2;
  1863.  
  1864.         if ($number_of_tokens == -1) {
  1865.             $arraysize = $arr['len'];
  1866.         } else {
  1867.             $arraysize = $number_of_tokens;
  1868.         }
  1869.         $typearr   = array();
  1870.         if ($arraysize >= 0) {
  1871.             $typearr[0] = '';
  1872.             $typearr[1] = '';
  1873.             $typearr[2] = '';
  1874.             //$typearr[3] = $arr[0]['type'];
  1875.             $typearr[3] = $arr[$start_token]['type'];
  1876.         }
  1877.  
  1878.         $in_priv_list = FALSE;
  1879.         for ($i = $start_token; $i < $arraysize; $i++) {
  1880. // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
  1881.             $before = '';
  1882.             $after  = '';
  1883.             $indent = 0;
  1884.             // array_shift($typearr);
  1885.             /*
  1886.             0 prev2
  1887.             1 prev
  1888.             2 current
  1889.             3 next
  1890.             */
  1891.             if (($i + 1) < $arraysize) {
  1892.                 // array_push($typearr, $arr[$i + 1]['type']);
  1893.                 $typearr[4] = $arr[$i + 1]['type'];
  1894.             } else {
  1895.                 //array_push($typearr, NULL);
  1896.                 $typearr[4] = '';
  1897.             }
  1898.  
  1899.             for ($j=0; $j<4; $j++) {
  1900.                 $typearr[$j] = $typearr[$j + 1];
  1901.             }
  1902.  
  1903.             switch ($typearr[2]) {
  1904.                 case 'white_newline':
  1905.                     $before     = '';
  1906.                     break;
  1907.                 case 'punct_bracket_open_round':
  1908.                     $bracketlevel++;
  1909.                     $infunction = FALSE;
  1910.                     // Make sure this array is sorted!
  1911.                     if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
  1912.                         || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
  1913.                         || (($typearr[0] == 'alpha_reservedWord')
  1914.                             && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
  1915.                         || (($typearr[1] == 'alpha_reservedWord')
  1916.                             && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
  1917.                         ) {
  1918.                         $functionlevel++;
  1919.                         $infunction = TRUE;
  1920.                         $after      .= ' ';
  1921.                     } else {
  1922.                         $indent++;
  1923.                         $after      .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
  1924.                     }
  1925.                     break;
  1926.                 case 'alpha_identifier':
  1927.                     if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
  1928.                         $after      = '';
  1929.                         $before     = '';
  1930.                     }
  1931.                     if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
  1932.                         $after      .= ' ';
  1933.                     }
  1934.                     break;
  1935.                 case 'punct_qualifier':
  1936.                     $before         = '';
  1937.                     $after          = '';
  1938.                     break;
  1939.                 case 'punct_listsep':
  1940.                     if ($infunction == TRUE) {
  1941.                         $after      .= $space_punct_listsep_function_name;
  1942.                     } else {
  1943.                         $after      .= $space_punct_listsep;
  1944.                     }
  1945.                     break;
  1946.                 case 'punct_queryend':
  1947.                     if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
  1948.                         $after     .= $html_line_break;
  1949.                         $after     .= $html_line_break;
  1950.                     }
  1951.                     $space_punct_listsep               = ' ';
  1952.                     $space_punct_listsep_function_name = ' ';
  1953.                     $space_alpha_reserved_word         = ' ';
  1954.                     $in_priv_list                      = FALSE;
  1955.                     break;
  1956.                 case 'comment_mysql':
  1957.                 case 'comment_ansi':
  1958.                     $after         .= $html_line_break;
  1959.                     break;
  1960.                 case 'punct':
  1961.                     $before         .= ' ';
  1962.                     // workaround for
  1963.                     // select * from mytable limit 0,-1
  1964.                     // (a side effect of this workaround is that
  1965.                     // select 20 - 9
  1966.                     // becomes
  1967.                     // select 20 -9
  1968.                     // )
  1969.                     if ($typearr[3] != 'digit_integer') {
  1970.                        $after        .= ' ';
  1971.                     }
  1972.                     break;
  1973.                 case 'punct_bracket_close_round':
  1974.                     $bracketlevel--;
  1975.                     if ($infunction == TRUE) {
  1976.                         $functionlevel--;
  1977.                         $after     .= ' ';
  1978.                         $before    .= ' ';
  1979.                     } else {
  1980.                         $indent--;
  1981.                         $before    .= ($mode != 'query_only' ? '</div>' : ' ');
  1982.                     }
  1983.                     $infunction    = ($functionlevel > 0) ? TRUE : FALSE;
  1984.                     break;
  1985.                 case 'alpha_columnType':
  1986.                     if ($typearr[3] == 'alpha_columnAttrib') {
  1987.                         $after     .= ' ';
  1988.                     }
  1989.                     if ($typearr[1] == 'alpha_columnType') {
  1990.                         $before    .= ' ';
  1991.                     }
  1992.                     break;
  1993.                 case 'alpha_columnAttrib':
  1994.  
  1995.                     // ALTER TABLE tbl_name AUTO_INCREMENT = 1
  1996.                     // COLLATE LATIN1_GENERAL_CI DEFAULT
  1997.                     if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
  1998.                         $before .= ' ';
  1999.                     }
  2000.                     if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
  2001.                         $after     .= ' ';
  2002.                     }
  2003.                     // workaround for
  2004.                     // select * from mysql.user where binary user="root"
  2005.                     // binary is marked as alpha_columnAttrib
  2006.                     // but should be marked as a reserved word
  2007.                     if (strtoupper($arr[$i]['data']) == 'BINARY'
  2008.                       && $typearr[3] == 'alpha_identifier') {
  2009.                         $after     .= ' ';
  2010.                     }
  2011.                     break;
  2012.                 case 'alpha_reservedWord':
  2013.                     $arr[$i]['data'] = strtoupper($arr[$i]['data']);
  2014.                     if ((($typearr[1] != 'alpha_reservedWord')
  2015.                         || (($typearr[1] == 'alpha_reservedWord')
  2016.                             && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
  2017.                         && ($typearr[1] != 'punct_level_plus')
  2018.                         && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
  2019.                         // do not put a space before the first token, because
  2020.                         // we use a lot of eregi() checking for the first
  2021.                         // reserved word at beginning of query
  2022.                         // so do not put a newline before
  2023.                         //
  2024.                         // also we must not be inside a privilege list
  2025.                         if ($i > 0) {
  2026.                             // the alpha_identifier exception is there to
  2027.                             // catch cases like
  2028.                             // GRANT SELECT ON mydb.mytable TO myuser@localhost
  2029.                             // (else, we get mydb.mytableTO )
  2030.                             //
  2031.                             // the quote_single exception is there to
  2032.                             // catch cases like
  2033.                             // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
  2034.                             //
  2035.                             // TODO: fix all cases and find why this happens
  2036.  
  2037.                             if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
  2038.                                 $before    .= $space_alpha_reserved_word;
  2039.                             }
  2040.                         } else {
  2041.                         // on first keyword, check if it introduces a
  2042.                         // privilege list
  2043.                             if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
  2044.                                 $in_priv_list = TRUE;
  2045.                             }
  2046.                         }
  2047.                     } else {
  2048.                         $before    .= ' ';
  2049.                     }
  2050.  
  2051.                     switch ($arr[$i]['data']) {
  2052.                         case 'CREATE':
  2053.                             if (!$in_priv_list) {
  2054.                                 $space_punct_listsep       = $html_line_break;
  2055.                                 $space_alpha_reserved_word = ' ';
  2056.                             }
  2057.                             break;
  2058.                         case 'EXPLAIN':
  2059.                         case 'DESCRIBE':
  2060.                         case 'SET':
  2061.                         case 'ALTER':
  2062.                         case 'DELETE':
  2063.                         case 'SHOW':
  2064.                         case 'DROP':
  2065.                         case 'UPDATE':
  2066.                         case 'TRUNCATE':
  2067.                         case 'ANALYZE':
  2068.                         case 'ANALYSE':
  2069.                             if (!$in_priv_list) {
  2070.                                 $space_punct_listsep       = $html_line_break;
  2071.                                 $space_alpha_reserved_word = ' ';
  2072.                             }
  2073.                             break;
  2074.                         case 'INSERT':
  2075.                         case 'REPLACE':
  2076.                             if (!$in_priv_list) {
  2077.                                 $space_punct_listsep       = $html_line_break;
  2078.                                 $space_alpha_reserved_word = $html_line_break;
  2079.                             }
  2080.                             break;
  2081.                         case 'VALUES':
  2082.                             $space_punct_listsep       = ' ';
  2083.                             $space_alpha_reserved_word = $html_line_break;
  2084.                             break;
  2085.                         case 'SELECT':
  2086.                             $space_punct_listsep       = ' ';
  2087.                             $space_alpha_reserved_word = $html_line_break;
  2088.                             break;
  2089.                         default:
  2090.                             break;
  2091.                     } // end switch ($arr[$i]['data'])
  2092.  
  2093.                     $after         .= ' ';
  2094.                     break;
  2095.                 case 'digit_integer':
  2096.                 case 'digit_float':
  2097.                 case 'digit_hex':
  2098.                     //TODO: could there be other types preceding a digit?
  2099.                     if ($typearr[1] == 'alpha_reservedWord') {
  2100.                         $after .= ' ';
  2101.                     }
  2102.                     if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
  2103.                         $after     .= ' ';
  2104.                     }
  2105.                     if ($typearr[1] == 'alpha_columnAttrib') {
  2106.                         $before .= ' ';
  2107.                     }
  2108.                     break;
  2109.                 case 'alpha_variable':
  2110.                     // other workaround for a problem similar to the one
  2111.                     // explained below for quote_single
  2112.                     if (!$in_priv_list) {
  2113.                         $after      = ' ';
  2114.                     }
  2115.                     break;
  2116.                 case 'quote_double':
  2117.                 case 'quote_single':
  2118.                     // workaround: for the query
  2119.                     // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
  2120.                     // the @ is incorrectly marked as alpha_variable
  2121.                     // in the parser, and here, the '%' gets a blank before,
  2122.                     // which is a syntax error
  2123.                     if ($typearr[1] !='alpha_variable') {
  2124.                         $before        .= ' ';
  2125.                     }
  2126.                     if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
  2127.                         $after     .= ' ';
  2128.                     }
  2129.                     break;
  2130.                 case 'quote_backtick':
  2131.                     if ($typearr[3] != 'punct_qualifier') {
  2132.                         $after     .= ' ';
  2133.                     }
  2134.                     if ($typearr[1] != 'punct_qualifier') {
  2135.                         $before    .= ' ';
  2136.                     }
  2137.                     break;
  2138.                 default:
  2139.                     break;
  2140.             } // end switch ($typearr[2])
  2141.  
  2142. /*
  2143.             if ($typearr[3] != 'punct_qualifier') {
  2144.                 $after             .= ' ';
  2145.             }
  2146.             $after                 .= "\n";
  2147. */
  2148.             $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
  2149.         } // end for
  2150.         if ($mode=='color') {
  2151.             $str .= '</span>';
  2152.         }
  2153.  
  2154.         return $str;
  2155.     } // end of the "PMA_SQP_formatHtml()" function
  2156. }
  2157.  
  2158. /**
  2159.  * Builds a CSS rule used for html formatted SQL queries
  2160.  *
  2161.  * @param  string  The class name
  2162.  * @param  string  The property name
  2163.  * @param  string  The property value
  2164.  *
  2165.  * @return string  The CSS rule
  2166.  *
  2167.  * @access public
  2168.  *
  2169.  * @see    PMA_SQP_buildCssData()
  2170.  */
  2171. function PMA_SQP_buildCssRule($classname, $property, $value)
  2172. {
  2173.     $str     = '.' . $classname . ' {';
  2174.     if ($value != '') {
  2175.         $str .= $property . ': ' . $value . ';';
  2176.     }
  2177.     $str     .= '}' . "\n";
  2178.  
  2179.     return $str;
  2180. } // end of the "PMA_SQP_buildCssRule()" function
  2181.  
  2182.  
  2183. /**
  2184.  * Builds CSS rules used for html formatted SQL queries
  2185.  *
  2186.  * @return string  The CSS rules set
  2187.  *
  2188.  * @access public
  2189.  *
  2190.  * @global array   The current PMA configuration
  2191.  *
  2192.  * @see    PMA_SQP_buildCssRule()
  2193.  */
  2194. function PMA_SQP_buildCssData()
  2195. {
  2196.     global $cfg;
  2197.  
  2198.     $css_string     = '';
  2199.     foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
  2200.         $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
  2201.     }
  2202.  
  2203.     for ($i = 0; $i < 8; $i++) {
  2204.         $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
  2205.     }
  2206.  
  2207.     return $css_string;
  2208. } // end of the "PMA_SQP_buildCssData()" function
  2209.  
  2210. if ($is_minimum_common == FALSE) {
  2211.     /**
  2212.      * Gets SQL queries with no format
  2213.      *
  2214.      * @param  array   The SQL queries list
  2215.      *
  2216.      * @return string  The SQL queries with no format
  2217.      *
  2218.      * @access public
  2219.      */
  2220.     function PMA_SQP_formatNone($arr)
  2221.     {
  2222.         $formatted_sql = htmlspecialchars($arr['raw']);
  2223.         $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
  2224.  
  2225.         return $formatted_sql;
  2226.     } // end of the "PMA_SQP_formatNone()" function
  2227.  
  2228.  
  2229.     /**
  2230.      * Gets SQL queries in text format
  2231.      *
  2232.      * @param  array   The SQL queries list
  2233.      *
  2234.      * @return string  The SQL queries in text format
  2235.      *
  2236.      * @access public
  2237.      */
  2238.     function PMA_SQP_formatText($arr)
  2239.     {
  2240.         /**
  2241.          * TODO WRITE THIS!
  2242.          */
  2243.          return PMA_SQP_formatNone($arr);
  2244.     } // end of the "PMA_SQP_formatText()" function
  2245. } // end if: minimal common.lib needed?
  2246.  
  2247. ?>
  2248.